// 
//  Copyright © 2009 Jiří Zárevúcky <zarevucky.jiri@gmail.com>
// 
//  This program is free software: you can redistribute it and/or modify
//  it under the terms of the GNU Affero General Public License as
//  published by the Free Software Foundation, either version 3 of the
//  License, or (at your option) any later version.
// 
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU Affero General Public License for more details.
// 
//  You should have received a copy of the GNU Affero General Public License
//  along with this program.  If not, see <http://www.gnu.org/licenses/>.
// 
// 


using System;
using System.Collections;
using System.Collections.Generic;
using Galaxium.Protocol.Xmpp.Library.Xml;
using Galaxium.Protocol.Xmpp.Library.Utility;

namespace Galaxium.Protocol.Xmpp.Library.Core
{
	public enum SubscriptionState { None, To, From, Both, Remove }
	
	public class RosterItem: Element
	{
		private class GroupCollection: ICollection<string>
		{
			private Element _elm;

			public GroupCollection (Element elm)
			{
				_elm = elm;
			}

			public void SetGroups (IEnumerable<string> groups)
			{
				Clear ();
				foreach (string group in groups)
					Add (group);
			}
		
			public bool IsReadOnly {
				get { return false; }
			}

			public int Count {
				get {
					int i = 0;
					foreach (Element elm in _elm)
						if (elm.Name == "group" && !String.IsNullOrEmpty (elm.Text)) i ++;
					return i;
				}
			}
			
			public void Add (string item)
			{
				if (item == null)
					throw new ArgumentNullException ();
				if (item == String.Empty)
					throw new ArgumentException ();
				var grp = new Element ("group");
				grp.Text = item;
				_elm.AppendChild (grp);
			}

			public bool Remove (string item)
			{
				throw new NotSupportedException ();
			}
			
			public void Clear ()
			{
				_elm.Clear ();
			}

			public bool Contains (string item)
			{
				foreach (string group in this)
					if (group == item) return true;
				return false;
			}

			public void CopyTo (string[] array, int arrayIndex)
			{
				foreach (string group in this) {
					array [arrayIndex] = group;
					arrayIndex ++;
				}
			}

			IEnumerator IEnumerable.GetEnumerator ()
			{
				return (this as IEnumerable<string>).GetEnumerator ();
			}

			IEnumerator<string> IEnumerable<string>.GetEnumerator ()
			{
				foreach (Element elm in _elm) {
					if (elm.Name == "group" && !String.IsNullOrEmpty (elm.Text))
						yield return elm.Text;
				}
			}
		}

		private GroupCollection _groups;
		
		public JabberID Jid {
			get { return new JabberID (GetAttribute ("jid")); }
			set { SetAttribute ("jid", value.ToString ()); }
		}

		public new string Name {
			get { return GetAttribute ("name"); }
			set { SetAttribute ("name", value); }
		}

		public SubscriptionState Subscription {
			get {
				var s = GetAttribute ("subscription");
				return (SubscriptionState) Enum.Parse (typeof (SubscriptionState), s, true);
			}
			set { SetAttribute ("subscription", value.ToString ().ToLower ()); }
		}

		public bool IsSubscribed {
			get {
				var sub = Subscription;
				return (sub == SubscriptionState.To ||
				        sub == SubscriptionState.Both);
			}
			set {
				var sub = Subscription;
				switch (sub) {
				case SubscriptionState.None:
					if (value) sub = SubscriptionState.To; break;
				case SubscriptionState.To:
					if (!value) sub = SubscriptionState.None; break;
				case SubscriptionState.From:
					if (value) sub = SubscriptionState.Both; break;
				case SubscriptionState.Both:
					if (!value) sub = SubscriptionState.From; break;
				case SubscriptionState.Remove:
					throw new InvalidOperationException ();
				}
				Subscription = sub;
			}
		}

		public bool HasSubscription {
			get {
				var sub = Subscription;
				return (sub == SubscriptionState.From ||
				        sub == SubscriptionState.Both);
			}
			set {
				var sub = Subscription;
				switch (sub) {
				case SubscriptionState.None:
					if (value) sub = SubscriptionState.From; break;
				case SubscriptionState.From:
					if (!value) sub = SubscriptionState.None; break;
				case SubscriptionState.To:
					if (value) sub = SubscriptionState.Both; break;
				case SubscriptionState.Both:
					if (!value) sub = SubscriptionState.To; break;
				case SubscriptionState.Remove:
					throw new InvalidOperationException ();
				}
				Subscription = sub;
			}
		}

		public bool Asking {
			get { return GetAttribute ("ask") == "subscribe"; }
			set { SetAttribute ("ask", value ? "subscribe" : null); }
		}
		
		public ICollection<string> Groups {
			get { return _groups; }
			set { SetGroups (value); }
		}
		
		public RosterItem ()
			:base ("item")
		{
			_groups = new GroupCollection (this);
		}
		
		public RosterItem (JabberID jid)
			:this ()
		{
			if (jid == null)
				throw new ArgumentNullException ();
			Jid = jid;
		}

		public RosterItem (Element elm)
			:base (elm.Clone ())
		{
			if (base.Name != "item")
				throw new ArgumentException ("The name of element is not 'item'.\n" +
				                             Serializer.Serialize (this));
			if (this ["jid"] == null)
				throw new ArgumentException ("JabberID is null.\n" +
				                             Serializer.Serialize (this));
			
			_groups = new GroupCollection (this);
		}

		public Iq CreatePush ()
		{
			var query = new Iq (IqType.Set);
			query.Query = new Element ("query", Namespaces.Roster);
			query.Query.AppendChild (this);
			return query;
		}

		public void SetGroups (IEnumerable<string> groups)
		{
			_groups.SetGroups (groups);
		}
	}
}
